package consumer;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;
import java.util.SortedMap;

import proxy.Content;
import proxy.SearchServerNode;
import proxy.ServerNode;
import proxy.UnitContainer;
import proxy.VideoUnit;
import simulation.Simulator;

/**
 * No additional user behavior, but contains code that judges the
 * 
 * @author Anita
 * 
 */
public class SearchConsumer extends Consumer {

	public SearchConsumer(int id, Simulator simulator, ServerNode server,
			Random rand) {
		super(id, simulator, server, rand);
		// range of #favorites between 0 and 40 for 95 % of users
		noFavorites = (int) Math.round((getRand().nextGaussian() * 22) + 40);
		if (noFavorites < noViewings) {
			noFavorites = noViewings;
		} else if (noFavorites > simulator.getTagDirectory().size()) {
			noFavorites = simulator.getTagDirectory().size() - 1;
		}
	}

	@Override
	public void processMixed() {
		boolean newRequest = false;
		boolean delivery = false;
		ArrayList<UnitContainer> ucs = new ArrayList<UnitContainer>();

		ArrayList<Viewing> seq1 = new ArrayList<Viewing>();
		ArrayList<Viewing> seq2 = new ArrayList<Viewing>();
		int reqsize;
		if (viewings.size() == 0) {
			// new request
			generateNewRequest();
			reqsize = viewings.size();
			((SearchServerNode) server).countRequest(viewings);
			newRequest = true;
			splitupinSeqs(seq1, seq2, reqsize);
		} else {
			// check if presenting mode or delivery mode
			for (Viewing vi : viewings) {
				if (vi.getViewStartTime() == Double.MAX_VALUE) {
					delivery = true;
					break;
				}
			}
		}

		if (delivery || newRequest) {

			for (Viewing vi : viewings) {
				ArrayList<UnitContainer> foundUcs = server
						.searchPresentUnits(vi.getVideoUnit().getContent());
				if (foundUcs.size() > 0) {
					boolean alreadyThere = false;
					for (UnitContainer uc : ucs) {
						if (uc.getVideoUnit()
								.getContent()
								.equals(foundUcs.get(0).getVideoUnit()
										.getContent())) {
							alreadyThere = true;
							break;
						}
					}
					if (!alreadyThere) {
						// lock unit (set is in use)
						foundUcs.get(0).viewedUntil = vi.getViewEndTime();
						ucs.add(foundUcs.get(0));
						// already here, so do not need to increase interest of
						// this tag
						if (seq1.contains(vi)) {
							seq1.remove(vi);
						} else {
							seq2.remove(vi);
						}
					}
				}
			}

			if (ucs.size() == viewings.size()) {
				// all there - start to present all
				splitupinSeqs(seq1, seq2, viewings.size());
				presentMixedUnits(ucs, seq1);
			} else {
				server.increaseInterest(seq1, newRequest, true);
				if (seq2.size() > 0) {
					server.increaseInterest(seq2, newRequest, true);
				}
			}
		} else {
			// presenting or presentation done

			for (int i = 0; i < viewings.size(); i++) {
				if (simulator.getSimTime() >= viewings.get(i).getViewEndTime()) {
					if (getRand().nextDouble() < getpMindChange()) {
						likesContent = Arrays.copyOf(viewings.get(i)
								.getVideoUnit().getContent().content,
								likesContent.length);
						chooseFavorites();
					}
					viewings.remove(i);
				} else {
					// presenting ... nothing else to do but statistics
					addUnitUtilizationStat();
					return;
				}
			}
			if (viewings.size() == 0) {
				simulator.requestFulfilledCounter++;
			}
		}

	}

	private void splitupinSeqs(ArrayList<Viewing> seq1,
			ArrayList<Viewing> seq2, int reqsize) {
		if (reqsize == 1) {
			seq1.add(viewings.get(0));
		} else {
			for (int i = 0; i < viewings.size() / 2; i++) {
				seq1.add(viewings.get(i));
				seq2.add(viewings.get(viewings.size() - i - 1));
			}
			if (reqsize % 2 != 0) {
				seq2.add(viewings.get(viewings.size() / 2 + 1));
			}
		}
	}

	@Override
	public void processParallel() {

		boolean newRequest = false;
		boolean delivery = false;

		if (viewings.size() == 0) {
			// new request
			generateNewRequest();
			newRequest = true;
			((SearchServerNode) server).countRequest(viewings);
		} else {
			// check if presenting mode or delivery mode
			for (Viewing vi : viewings) {
				if (vi.getViewStartTime() == Double.MAX_VALUE) {
					delivery = true;
					break;
				}
			}
		}

		if (delivery || newRequest) {
			// delivery mode
			ArrayList<UnitContainer> ucs = new ArrayList<UnitContainer>();
			ArrayList<Viewing> stillToDeliver = new ArrayList<Viewing>();

			for (Viewing vi : viewings) {

				ArrayList<UnitContainer> foundUc = server.searchPresentUnits(vi
						.getVideoUnit().getContent());
				if (foundUc.size() > 0) {
					boolean alreadyThere = false;
					for (UnitContainer uc : ucs) {
						if (uc.getVideoUnit()
								.getContent()
								.equals(foundUc.get(0).getVideoUnit()
										.getContent())) {
							alreadyThere = true;
							break;
						}
					}
					if (alreadyThere) {
						stillToDeliver.add(vi);

					} else {
						foundUc.get(0).viewedUntil = vi.getViewEndTime();
						ucs.add(foundUc.get(0));
					}
				} else {
					stillToDeliver.add(vi);
				}
			}

			if (stillToDeliver.size() == 0) {
				// all there - start to present all
				presentParallelUnits(ucs);
			} else {
				server.increaseInterest(stillToDeliver, newRequest, false);
			}
		} else {

			// presenting or presentation done
			for (int i = 0; i < viewings.size(); i++) {
				if (simulator.getSimTime() >= viewings.get(i).getViewEndTime()) {
					// maybe adjust taste
					if (getRand().nextDouble() < getpMindChange()) {
						likesContent = Arrays.copyOf(viewings.get(i)
								.getVideoUnit().getContent().content,
								likesContent.length);
						chooseFavorites();
					}
					viewings.remove(i);
				} else {
					addUnitUtilizationStat();
					// presenting ... nothing else to do
					return;
				}
			}
			if (viewings.size() == 0) {
				simulator.requestFulfilledCounter++;
			}
		}
	}

	@Override
	public void processSequence() {
		// check if there is something to present
		if (currentlyViewing == null) {

			while (viewings.size() == 0) {
				generateNewRequest();
				// reset deadlines and requesttime to playbacktime
				for (int i = 1; i < viewings.size(); i++) {
					Viewing current = viewings.get(i);
					Viewing before = viewings.get(i - 1);
					double playLength = before.getVideoUnit().playLength();
					double deadline = before.getDeadline() + playLength;
					current.setDeadline(deadline);
					current.setRequestTime(before.getRequestTime() + playLength);
				}
				server.increaseInterest(viewings, true, true);
				((SearchServerNode) server).countRequest(viewings);
			}
			// there is a request that has to be fulfilled
			// check if the unit is local and can be started
			Viewing next = viewings.get(0);

			ArrayList<UnitContainer> uc = server.searchPresentUnits(next
					.getVideoUnit().getContent());
			List<Viewing> currentlyDemanding;
			if (uc.size() > 0) {
				// is local, but not started yet
				presentSequentialLocalUnit(next, uc.get(0));
				currentlyDemanding = viewings.subList(1, viewings.size());
			} else {
				// check deadline - if missed try to present next unit
				if (checkDeadline(next)) {
					viewings.remove(next);
					if (viewings.size() > 0) {
						Viewing nextNext = viewings.get(0);
						nextNext.setRequestTime(simulator.getSimTime());
						nextNext.setDeadline(simulator.getSimTime()
								+ minDeadline);
						tryToPresentNextUnit(nextNext);
					} else {
						cleanupViewings();
					}
				}
				currentlyDemanding = viewings;
			}
			server.increaseInterest(currentlyDemanding, false, true);

		} else {

			// check if unit is done, delete from list
			if (simulator.getSimTime() >= currentlyViewing.getViewEndTime()) {

				if (getRand().nextDouble() < getpMindChange()) {
					likesContent = Arrays.copyOf(currentlyViewing
							.getVideoUnit().getContent().content,
							likesContent.length);
					chooseFavorites();
				}
				handleSeqUnitPresentationDone();
			} else {
				// currently viewing a unit, increase for other units
				server.increaseInterest(viewings.subList(1, viewings.size()),
						false, true);

			}
		}
	}

	@Override
	protected void tryToPresentNextUnit(Viewing next) {
		List<UnitContainer> uc = server.searchPresentUnits(next.getVideoUnit()
				.getContent());
		if (uc.size() > 0) {
			presentSequentialLocalUnit(next, uc.get(0));
		}
	}

	@Override
	/**
	 * Chooses the favorites according to the treeDistance and the popularity of a tag
	 */
	public void chooseFavorites() {
		// empty favorites list
		sumDistances = 0.0;
		// has to be videoUnits
		LinkedList<VideoUnit> distanceList = new LinkedList<VideoUnit>();

		// calculate all distances of tags
		// expects that for each tag a unit exists -> see
		// simulator.initStorage()
		SortedMap<Content, ArrayList<VideoUnit>> tagVDir = simulator
				.getTagVideoDirectory();
		for (Content tag : tagVDir.keySet()) {
			// take any unit that matches this tag for the request it does not
			// matter :)
			for (VideoUnit vu : tagVDir.get(tag)) {
				vu.calculateTreeDistance(likesContent);
				distanceList.add((VideoUnit) vu.clone());
			}
		}
		// sort according to distances
		Collections.sort(distanceList);

		favoriteList = new LinkedList<VideoUnit>();

		interestRadius = distanceList.get(noFavorites - 1).getDistance();
		int index = noFavorites;
		while (interestRadius == 0 && index < distanceList.size()) {
			interestRadius = distanceList.get(index).getDistance();
			index++;
		}

		for (int i = 0; i < index; i++) {
			// probability to view a video is based on following formula
			favoriteList.add(distanceList.get(i));
			sumDistances += (interestRadius - distanceList.get(i).getDistance());
		}
		if (sumDistances == 0) {
			throw new RuntimeException("sumDistances zero!");
		}
	}

	protected void handleSeqUnitPresentationDone() {
		viewings.remove(0);
		currentlyViewing = null;
		// interpacket delay (startup delay!)
		if (viewings.size() > 0) {
			Viewing next = viewings.get(0);
			next.setRequestTime(simulator.getSimTime());
			next.setDeadline(simulator.getSimTime() + minDeadline);
			// try to present next unit
			tryToPresentNextUnit(next);
		} else {
			// request done
			cleanupViewings();
		}
	}

}
